-module(yaws_content_negotiation).
-export([compute_rsva/2, parse_accept_field/1, match_media_type/2]).

-include("../include/yaws_api.hrl").

%% TODO:  analyze impact of spaces in headers; when/where to strip?

%% RFC 2296
compute_rsva(Arg, VariantList) ->
	Headers = Arg#arg.headers,
	Accept = parse_accept_field(Headers#headers.accept),
	% move these into #headers{} !
	AcceptCharset = parse_accept_field(get_other_header('Accept-Charset', Headers)),
	AcceptLanguage = parse_accept_field(get_other_header('Accept-Language', Headers)),
	AcceptFeatures = [], % yet to be implemented
	RankedVariants = overall_quality(VariantList, Accept, AcceptCharset, AcceptLanguage, AcceptFeatures),
	case determine_result(RankedVariants) of
		none ->
			% TODO:  build Alternates: header field
			VariantList;
		URI ->
			% TODO:  must be neighbour variant (same root as request)
			URI
	end.
	
get_other_header(Name, Headers) ->
	case lists:keysearch(Name, 3, Headers) of
		{value, {_,_,Name,_,Field}} -> Field;
		false -> undefined
	end.

determine_result(V) ->
	determine_result(V, {[], {0,1}}).

determine_result([{URI,{Q,X}}|T], {_,{AccQ,_}}) when Q > AccQ ->
	determine_result(T, {URI,{Q,X}});
determine_result([_|T], Acc) ->
	determine_result(T, Acc);
determine_result([], {URI,{_,0}}) -> URI;
determine_result([], _) -> none.
	
overall_quality(VariantList, Accept, AcceptCharset, AcceptLanguage, AcceptFeatures) ->
	overall_quality(VariantList, Accept, AcceptCharset, AcceptLanguage, AcceptFeatures, []).

overall_quality([{URI,Qs,Attributes}|T], Accept, AcceptCharset, AcceptLanguage, AcceptFeatures, Acc) ->
	% QX = {Qvalue, [0=definitive, >0=speculative]}
	Qt = case {lists:keysearch(type, 1, Attributes), Accept} of
		{false, _} -> {1000, 0};
		{_, []} -> {1000, 1};
		{{value, {type, MediaType}}, Accept} ->
			match_media_type(MediaType, Accept)
	end,
	Qc = case {lists:keysearch(charset, 1, Attributes), AcceptCharset} of
		{false, _} -> {1000, 0};
		{_, []} -> {1000, 1};
		{{value, {charset, Charset}}, AcceptCharset} ->
			match_charset(Charset, AcceptCharset)
	end,
	Ql = case {lists:keysearch(language, 1, Attributes), AcceptLanguage} of
		{false, _} -> {1000, 0};
		{_, []} -> {1000, 1};
		{{value, {language, Languages}}, AcceptLanguage} ->
			match_language(Languages, AcceptLanguage)
	end,
	Qf = {1000, 0},  % Accept-Features:  not yet implemented
	Q = round5(Qs, Qt, Qc, Ql, Qf),
	overall_quality(T, Accept, AcceptCharset, AcceptLanguage, AcceptFeatures, [{URI, Q}|Acc]);
% fallback-variant
overall_quality([URI|T], Accept, AcceptCharset, AcceptLanguage, AcceptFeatures, Acc) 
		when is_list(URI) ->
	Q = {1, 0},  % {0.000001, definitive}
	overall_quality(T, Accept, AcceptCharset, AcceptLanguage, AcceptFeatures, [{URI, Q}|Acc]);
overall_quality([], _Accept, _AcceptCharset, _AcceptLanguage, _AcceptFeatures, Acc) -> Acc.

% this functions rounds to five decimal places but to avoid
% using floats we have 1 represent 0.000001 and 1000000 = 1.0
round5(Qs, {Qt, A}, {Qc, B}, {Ql, C}, {Qf, D}) ->
	Q = ((Qs * 1000) * (Qt * 1000) * (Qc * 1000) * (Ql * 1000) * (Qf * 1000)),
	{((Q div 10000000000000000000000000) * 10), (A+B+C+D)}.
	
match_media_type(MediaType, AcceptRanges) ->
	case lists:keysearch(MediaType, 1, AcceptRanges) of
		{ok, {_, Q}} -> {Q, 0};
		false ->
			[SimpleMediaType|_] = yaws:split_sep(MediaType, $;),
			match_media_type_simple(SimpleMediaType, AcceptRanges)
	end.

match_media_type_simple(SimpleMediaType, AcceptRanges) ->
	case lists:keysearch(SimpleMediaType, 1, AcceptRanges) of
		{ok, {_, Q}} -> {Q, 0};
		false ->
			[Type|_] = yaws:split_sep(SimpleMediaType, $/),
			match_media_type_only(Type, AcceptRanges)
	end.

match_media_type_only(Type, AcceptRanges) ->
	case lists:keysearch(Type++"/*", 1, AcceptRanges) of
		{ok, {_, Q}} -> {Q, 1};
		false ->
			case lists:keysearch("*/*", 1, AcceptRanges) of
				{ok, {_, Q}} -> {Q, 1};
				false -> {0, 0}
			end
	end.

match_charset(Charset, AcceptCharset) ->
	case lists:keysearch(Charset, 1, AcceptCharset) of
		{ok, {_, Q}} -> {Q, 0};
		false -> {0, 0}
	end.

match_language(Languages, AcceptLanguage) ->
	match_language(Languages, AcceptLanguage, 0).

match_language([Language|T], AcceptLanguage, Qacc) ->
	Qacc1 = case lists:keysearch(Language, 1, AcceptLanguage) of
		{ok, {_, Q1}} when Q1 > Qacc -> Q1;
		_ -> Qacc
	end,
	Qacc2 = case yaws:split_sep(Language, $-) of
		[H|T] when length(T) > 0 ->
			case lists:keysearch(H, 1, AcceptLanguage) of
				{ok, {_, Q2}} when Q2 > Qacc1 -> Q2;
				_ -> Qacc1
			end;
		[Language] -> Qacc1
	end,
	match_language(T, AcceptLanguage, Qacc2);
match_language([], AcceptLanguage, Qacc) ->
	case lists:keysearch("*", 1, AcceptLanguage) of
		{ok, {_, Q}} when Q > Qacc -> {Q, 1};
		_ -> {Qacc, 0}
	end.

parse_accept_field(undefined) -> [];
parse_accept_field(Field) ->
	parse_accept_field(yaws:split_sep(Field, $,), []).

parse_accept_field([H|T], Acc) ->
	R = parse_accept_element(yaws:split_sep(H, $;)),
	parse_accept_field(T, Acc ++ [R]);
parse_accept_field([], Acc) -> Acc.

parse_accept_element(L) ->
	parse_accept_element(L, []).

parse_accept_element(["q="++Q|_], Acc) ->
	{Acc, yaws:parse_qvalue(Q)};
parse_accept_element([H|T], []) ->
	parse_accept_element(T, H);
parse_accept_element([H|T], Acc) ->
	parse_accept_element(T, Acc++";"++H);
parse_accept_element([], Acc) ->
	{Acc, 1000}.
	

